package storage

import (
	"bufio"
	"bytes"
	"io"

	"gitee.com/thubcc/p2pgit/types"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/object"
)

var (
	headerpgp string = "gpgsig"
)

// Commit points to a single tree, marking it as what the project looked like
// at a certain point in time. It contains meta-information about that point
// in time, such as a timestamp, the author of the changes since the last
// commit, a pointer to the previous commit(s), etc.
// http://shafiulazam.com/gitbook/1_the_git_object_model.html
type Commit struct {
	// Hash of the commit object.
	Hash types.Hash
	// Author is the original author of the commit.
	Author object.Signature
	// Committer is the one performing the commit, might be different from
	// Author.
	Committer object.Signature
	// PGPSignature is the PGP signature of the commit.
	PGPSignature string
	// Message is the commit message, contains arbitrary text.
	Message string
	// TreeHash is the hash of the root tree of the commit.
	TreeHash types.Hash
	// ParentHashes are the hashes of the parent commits of the commit.
	ParentHashes []types.Hash

	s ObjectStorer
}

// GetCommit gets a commit from an object storer and decodes it.
func GetCommit(s ObjectStorer, h types.Hash) (*Commit, error) {
	o, err := s.GetRawObject(h)
	if err != nil {
		return nil, err
	}
	return DecodeCommit(s, o)
}

// DecodeCommit decodes an encoded object into a *Commit and associates it to
// the given object storer.
func DecodeCommit(s ObjectStorer, o *RawObject) (*Commit, error) {
	c := &Commit{s: s}
	if err := c.Decode(o); err != nil {
		return nil, err
	}

	return c, nil
}

// Decode transforms a plumbing.EncodedObject into a Commit struct.
func (c *Commit) Decode(o *RawObject) (err error) {
	if o.Type() != plumbing.CommitObject {
		return object.ErrUnsupportedObject
	}

	c.Hash = o.Hash()

	reader := o.Reader()

	r := bufPool.Get().(*bufio.Reader)
	defer bufPool.Put(r)
	r.Reset(reader)

	var message bool
	var pgpsig bool
	for {
		line, err := r.ReadBytes('\n')
		if err != nil && err != io.EOF {
			return err
		}

		if pgpsig {
			if len(line) > 0 && line[0] == ' ' {
				line = bytes.TrimLeft(line, " ")
				c.PGPSignature += string(line)
				continue
			} else {
				pgpsig = false
			}
		}

		if !message {
			line = bytes.TrimSpace(line)
			if len(line) == 0 {
				message = true
				continue
			}

			split := bytes.SplitN(line, []byte{' '}, 2)

			var data []byte
			if len(split) == 2 {
				data = split[1]
			}

			switch string(split[0]) {
			case "tree":
				c.TreeHash = types.NewHash(string(data))
			case "parent":
				c.ParentHashes = append(c.ParentHashes, types.NewHash(string(data)))
			case "author":
				c.Author.Decode(data)
			case "committer":
				c.Committer.Decode(data)
			case headerpgp:
				c.PGPSignature += string(data) + "\n"
				pgpsig = true
			}
		} else {
			c.Message += string(line)
		}

		if err == io.EOF {
			return nil
		}
	}
}
